from dataclasses import dataclass
from datetime import datetime

from fastapi import status
from fastapi.exceptions import HTTPException

from app.controllers.base_controller import BaseController
from app.models.region_models import Region
from app.models.weather_models import Weather
from app.models.weatherforecast_models import WeatherForecast
from app.schemas.weather_schemas import (CreateWeatherRequestSchema,
                                         EditWeatherRequest,
                                         EditWeatherResponse,
                                         GetWeatherForRegion,
                                         GetWeatherResponseSchema,
                                         RegionDeleteWeatherResponse)


@dataclass
class CreateWeatherController(BaseController):
    request: CreateWeatherRequestSchema

    async def execute(self) -> GetWeatherResponseSchema:
        if self.request.regionId is None or self.request.regionId <= 0:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)

        try:
            datetime.fromisoformat(str(self.request.measurementDateTime))
        except Exception:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)

        if self.request.windSpeed < 0:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)

        if self.request.precipitationAmount < 0:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)

        region = await Region.get_or_none(id=self.request.regionId)

        if region is None:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)

        for forecast_id in self.request.weatherForecast:
            forecast = await WeatherForecast.get_or_none(id=forecast_id, region=region)
            if forecast is None:
                raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)

        new_weather = await Weather.create(
            temperature=self.request.temperature,
            humidity=self.request.humidity,
            wind_speed=self.request.windSpeed,
            weather_condition=self.request.weatherCondition,
            precipitation_amount=self.request.precipitationAmount,
            measurement_date_time=self.request.measurementDateTime,
        )

        region.weather = new_weather
        await region.save()

        forecasts = []

        for forecast_id in self.request.weatherForecast:
            forecast = await WeatherForecast.get(id=forecast_id)
            forecast.weather = new_weather
            await forecast.save()
            forecasts.append(forecast.id)

        return GetWeatherResponseSchema(
            id=new_weather.id,
            temperature=new_weather.temperature,
            humidity=new_weather.humidity,
            windSpeed=new_weather.wind_speed,
            weatherCondition=new_weather.weather_condition,
            precipitationAmount=new_weather.precipitation_amount,
            measurementDateTime=new_weather.measurement_date_time,
            weatherForecast=forecasts,
        )


@dataclass
class GetWeatherById(BaseController):
    region_id: int

    async def execute(self) -> GetWeatherForRegion:
        if self.region_id is None or self.region_id <= 0:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)

        region = await Region.get_or_none(id=self.region_id)

        if region is None:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)

        weather = await region.weather
        if weather is None:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
        forecasts = []
        for forecast in await weather.weather_forecasts:
            forecast_region = await WeatherForecast.get_or_none(
                id=forecast.id, region=region
            )
            if forecast_region is None:
                raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
            forecasts.append(forecast.id)

        return GetWeatherForRegion(
            id=region.id,
            regionName=region.name,
            temperature=weather.temperature,
            humidity=weather.humidity,
            windSpeed=weather.wind_speed,
            weatherCondition=weather.weather_condition,
            precipitationAmount=weather.precipitation_amount,
            measurementDateTime=weather.measurement_date_time,
            weatherForecast=forecasts,
        )


@dataclass
class DeleteWeather(BaseController):
    region_id: int

    async def execute(self) -> None:
        if self.region_id is None or self.region_id <= 0:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)

        region = await Region.get_or_none(id=self.region_id)

        if region is None:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)

        weather = await region.weather

        await weather.delete()


@dataclass
class AddWeatherToRegion(BaseController):
    region_id: int
    weather_id: int

    async def execute(self) -> GetWeatherForRegion:
        if self.region_id is None or self.region_id <= 0:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
        if self.weather_id is None or self.weather_id <= 0:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)

        region = await Region.get_or_none(id=self.region_id)
        weather = await Weather.get_or_none(id=self.weather_id)

        if region is None or weather is None:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)

        region.weather = weather

        await region.save()

        forecasts = []

        for forecast in await weather.weather_forecasts:
            forecast_region = await WeatherForecast.get_or_none(
                id=forecast.id, region=region
            )
            if forecast_region is None:
                raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
            forecasts.append(forecast.id)

        return GetWeatherForRegion(
            id=region.id,
            regionName=region.name,
            temperature=weather.temperature,
            humidity=weather.humidity,
            windSpeed=weather.wind_speed,
            weatherCondition=weather.weather_condition,
            precipitationAmount=weather.precipitation_amount,
            measurementDateTime=weather.measurement_date_time,
            weatherForecast=forecasts,
        )


@dataclass
class DeleteWeatherForRegion(BaseController):
    region_id: int
    weather_id: int

    async def execute(self) -> RegionDeleteWeatherResponse:
        if self.region_id is None or self.region_id <= 0:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
        if self.weather_id is None or self.weather_id <= 0:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)

        region = await Region.get_or_none(id=self.region_id)

        if region is None:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)

        region.weather = None

        await region.save()

        return RegionDeleteWeatherResponse(
            id=region.id,
            name=region.name,
            parentRegion=region.parentRegion,
            latitude=region.latitude,
            longitude=region.longitude,
        )


@dataclass
class EditWeatherController(BaseController):
    region_id: int
    request: EditWeatherRequest

    async def execute(self) -> EditWeatherResponse:
        if self.region_id is None or self.region_id <= 0:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)

        if self.request.precipitationAmount < 0:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)

        try:
            datetime.fromisoformat(str(self.request.measurementDateTime))
        except Exception:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)

        region = await Region.get_or_none(id=self.region_id)

        if region is None:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)

        weather = await region.weather

        if weather is None:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)

        for forecast_id in self.request.weatherForecast:
            forecast = await WeatherForecast.get_or_none(id=forecast_id, region=region)
            if forecast is None:
                raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)

        region.name = self.request.regionName
        weather.temperature = self.request.temperature
        weather.humidity = self.request.humidity
        weather.wind_speed = self.request.windSpeed
        weather.weather_condition = self.request.weatherCondition
        weather.precipitation_amount = self.request.precipitationAmount

        await region.save()
        await weather.save()

        forecasts = []

        for forecast_id in self.request.weatherForecast:
            forecast = await WeatherForecast.get(id=forecast_id)
            forecast.weather = weather
            await forecast.save()
            forecasts.append(forecast.id)

        return EditWeatherResponse(
            id=weather.id,
            regionId=region.id,
            regionName=region.name,
            temperature=weather.temperature,
            humidity=weather.humidity,
            windSpeed=weather.wind_speed,
            weatherCondition=weather.weather_condition,
            precipitationAmount=weather.precipitation_amount,
            measurementDateTime=weather.measurement_date_time,
            weatherForecast=forecasts,
        )
